#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <assert.h>
#include <pthread.h>

#include <include/pf_cache.h>
#include <include/pf_internal.h>

/* cache struct */
struct pf_cache
{
    /* Mutex to protect access to the structure */
    pthread_mutex_t mutex;
    /* Name of the cache objects in this cache (provided by the caller) */
    char *name;
    /* List of pointers to available buffers in this cache */
    void **ptr;
    /* The size of each element in this cache */
    size_t bufsize;
    /* preallocated object buffer begin */
    char *buff_begin;
    /* preallocated object buffer end */
    char *buff_end;
    /* max free elements in cache,0 meens no constaint */
    int max_free;
    /* The capacity of the list of elements */
    int freetotal;
    /* The current number of free elements */
    int freecurr;
    /* The constructor to be called each time we allocate more memory */
    pf_cache_constructor_f* constructor;
    /* The destructor to be called each time before we release memory */
    pf_cache_destructor_f* destructor;
};

/* reback object to cache */
#define PF_CACHE_FREE_OBJ(cache,ptr) \
do\
{\
    if (cache->destructor)\
    {\
        cache->destructor(pf_cache_get_object(ptr), NULL);\
    }\
    if( ((char *)(ptr) < cache->buff_begin)\
        || ((char *)(ptr) >= cache->buff_end))\
    {\
        free(ptr);\
    }\
}\
while(0)

/*********************************************************************************************
Function Name:  pf_cache_get_object
Description  :  get object
Inputs       :  void *ptr     : Buff pointer.
Outputs      :  return value  : Object pointer.
ErrorCodes   :  no
History      :
---------------------------------------------------------------------------------------------
Author                 Date                       Comments
lijz                   2011-6-24                  create
**********************************************************************************************/
static inline void* pf_cache_get_object(void *ptr);

/**********************************************************************************************/

pf_cache_t* pf_cache_create(const char* name,
                            size_t bufsize,
                            size_t align,
                            pf_cache_constructor_f* constructor,
                            pf_cache_destructor_f* destructor,
                            int initial_size,
                            int max_free)
{
    int i           = 0;
    pf_cache_t* ret = NULL;
    char* nm        = NULL;
    void** ptr      = NULL;
    char *int_buff  = NULL;
    void *object    = NULL;

    assert(NULL != name);
    assert(0 < bufsize);
    assert(0 < initial_size);
    assert(max_free >= initial_size || 0 == max_free);
    PF_UNUSED(align);

    ret = calloc(1, sizeof(pf_cache_t));
    nm = strdup(name);
    ptr = calloc(initial_size, sizeof(void*));

    /* pre-alloc object's buffer */
    int_buff = malloc(bufsize * initial_size);
    if (ret == NULL || nm == NULL || ptr == NULL ||int_buff == NULL 
        || pthread_mutex_init(&ret->mutex, NULL) == -1)
    {
        goto error_out;
    }

    ret->name        = nm;
    ret->ptr         = ptr;
    ret->bufsize     = bufsize;
    ret->freetotal   = initial_size;
    ret->max_free    = max_free;
    ret->buff_begin  = int_buff;
    ret->buff_end    = int_buff + (bufsize * initial_size);
    ret->constructor = constructor;
    ret->destructor  = destructor;

    /* pull pre-alloc object buffers into cache */
    for(i = 0; i< initial_size; i++)
    {
        ret->ptr[i] = int_buff + (bufsize*i);
        if(constructor)
        {
             object = pf_cache_get_object(ret->ptr[i]);
             if(constructor(object, NULL, 0) != 0)
             {
                  goto error_out;
             }
        }
    }

    ret->freecurr = initial_size;

    return ret;

error_out:

    /* destruct constructed */
    if(i && destructor)
    {
        do
        {
            object = pf_cache_get_object(ret->ptr[i]);
            destructor(object,0);
        }while(--i);
    }

    if(NULL != ret)
    {
        free(ret);
    }
    if(NULL != nm)
    {
        free(nm);
    }
    if(NULL != ptr)
    {
        free(ptr);
    }
    if(NULL != int_buff)
    {
        free(int_buff);
    }

    return NULL;
}

void pf_cache_destroy(pf_cache_t* cache)
{

    if(NULL == cache)
    {
        return;
    }

    while(cache->freecurr > 0)
    {
        void *ptr = cache->ptr[--cache->freecurr];
        PF_CACHE_FREE_OBJ(cache,ptr);
    }
    free(cache->name);
    free(cache->ptr);
    free(cache->buff_begin);
    pthread_mutex_destroy(&cache->mutex);
    free(cache);

    return;
}

void* pf_cache_alloc(pf_cache_t* cache)
{
    void *ret    = NULL;
    void *object = NULL;

    assert(cache != NULL);

    pthread_mutex_lock(&cache->mutex);
    if(cache->freecurr > 0)
    {
        ret = cache->ptr[--cache->freecurr];
        object = pf_cache_get_object(ret);
    }
    else
    {
        object = ret = malloc(cache->bufsize);
        if(ret != NULL)
        {
            object = pf_cache_get_object(ret);

            if(cache->constructor != NULL 
               && cache->constructor(object, NULL, 0) != 0)
            {
                free(ret);
                object = NULL;
            }
        }
    }
    pthread_mutex_unlock(&cache->mutex);

    return object;
}

void pf_cache_free(pf_cache_t* cache, void* ptr)
{
    size_t newtotal = -1;
    void** new_free = NULL;

    assert(cache != NULL);
    assert(ptr != NULL);

    pthread_mutex_lock(&cache->mutex);

    if(cache->max_free
       && (cache->freecurr >= cache->max_free))
    {
         PF_CACHE_FREE_OBJ(cache,ptr);
    }
    else if(cache->freecurr < cache->freetotal)
    {
        cache->ptr[cache->freecurr++] = ptr;
    }
    else
    {
        /* try to enlarge free connections array */
        newtotal = cache->freetotal * 2;
        new_free = realloc(cache->ptr, sizeof(char *) * newtotal);
        if (new_free)
        {
            cache->freetotal = newtotal;
            cache->ptr = new_free;
            cache->ptr[cache->freecurr++] = ptr;
        }
        else
        {
            PF_CACHE_FREE_OBJ(cache,ptr);
        }
    }
    pthread_mutex_unlock(&cache->mutex);

    return;
}

/**********************************************************************************************/

static inline void* pf_cache_get_object(void *ptr)
{
    return ptr;
}
